package pl.edu.agh.jemo.evolution.operator.crossover;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JPanel;

import pl.edu.agh.jemo.evolution.common.JemoRandom;
import pl.edu.agh.jemo.evolution.operator.common.DomainControl;
import pl.edu.agh.jemo.evolution.population.Population;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.panels.crossover.ClassicCrossoverConfigPanel;

/**
 * Base for many Crossover implementations.
 * It is recommended to prepare new Crossover operator extending ClassicCrossover, however it is not required.
 * Only implementation of Crossover interface is obligatory and this class acts as a helper with common crossover functionality like pairs of specimen choosing,
 * or domain handling provieded.
 * 
 * @author Marcin Zbijowski
 *
 */
public abstract class ClassicCrossover implements Crossover {
	
	/**
	 * Specifies how generated out of domain specimen are handled.
	 */
	protected DomainControl domainControl = DomainControl.MOVE_TO_BORDER;
	/**
	 * Chance that chosen pair will be recombinated.
	 */
	protected Double crossoverChance = .15;
	/**
	 * If this parameter is true, newly generated will be compared against parent, and two strongest will be returned.
	 */
	protected boolean improveSpecimen = false;

	/**
	 * Chooses specified number of pairs of specimen from population and replaces them with new ones.
	 * Number of pairs chosen is based on crossoverChance. Method assures that every specimen reference in pairs differ if possible.
	 * 
	 * @param population Population for which recombination should be conducted.
	 */
	@Override
	public void performCrossover(Population population) {
		Population newPopulation = new Population();
		if (population.size() < 2) {
			return;
		}
		// Will perform number of crossovers proportional to crossoverChance given
		for (int i = 0; i < population.size() * crossoverChance; i++) {
			int firstIndex = JemoRandom.getRandom().nextInt(population.size());
			Specimen firstParent = population.get(firstIndex);

			int secondIndex = JemoRandom.getRandom().nextInt(population.size());
			while (secondIndex == firstIndex) {
				secondIndex = JemoRandom.getRandom().nextInt(population.size());
			}
			
			Specimen secondParent = population.get(secondIndex);
			
			// removes from higher to lower index, so order won't be affected
			if (firstIndex > secondIndex) {
				population.remove(firstIndex);
				population.remove(secondIndex);
			} else {
				population.remove(secondIndex);
				population.remove(firstIndex);
			}
			Specimen[] recombinated = performCrossover(firstParent, secondParent); 
			newPopulation.add(recombinated[0]);
			newPopulation.add(recombinated[1]);
		}
		population.addAll(newPopulation);
	}

	/**
	 * For a given pair of specimen, returns new pair in an array.
	 * Method uses values of domainControl and improveSpecimen to validate generated specimen.
	 * 
	 * @param first Reference to first specimen to be recombinated.
	 * @param second Reference to second specimen to be recombinated.
	 * @return Array of Specimen generated from given. An array should contain new instances of Specimen class, and parent Specimen should remain unchanged.
	 */
	protected Specimen[] performCrossover(Specimen first, Specimen second) {
		
		Specimen[] result = new Specimen[2];
		result[0] = first.clone();
		result[1] = second.clone();

		result = recombinate(result[0], result[1]);
		
		switch (domainControl) {
			case GENERATE_NEXT:
				while ((!result[0].isInDomain()) || (!result[1].isInDomain())) {
					result = recombinate(first, second);
				}
				break;
			case MOVE_TO_BORDER:
				for (Specimen spec : result) {
					if (!spec.isInDomain()) {
						spec.moveToDomain();
					}
				}
				break;
			case IGNORE:
			default:
		}
		
		if (!improveSpecimen) {
			return result;
		}
		
		List<Specimen> sortList = new ArrayList<Specimen>();
		sortList.add(first);
		sortList.add(second);
		sortList.add(result[0]);
		sortList.add(result[1]);
		Collections.sort(sortList, Collections.reverseOrder());
		
		result[0] = sortList.get(0);
		result[1] = sortList.get(1);
		
		return result;
	}
	
	/**
	 * Returns new Specimen pair from given.
	 * This method is responsible genotype operations and is supposed to be overriden in crossover subclasses.
	 * 
	 * @param first Reference to first specimen to be recombinated.
	 * @param second Reference to second specimen to be recombinated.
	 * @return Array of Specimen generated from given. An array should contain new instaces of Specimen class, and parent Specimen should remain unchanged.
	 */
	protected abstract Specimen[] recombinate(Specimen first, Specimen second);		

	/**
	 * Returns currently set domain control policy.
	 * 
	 * @return DomainControl value set for crossover object
	 */
	public DomainControl getDomainControl() {
		return domainControl;
	}

	/**
	 * Sets new domain control policy for crossover.
	 * 
	 * @param domainControl New domain control policy value.
	 */
	public void setDomainControl(DomainControl domainControl) {
		this.domainControl = domainControl;
	}

	/**
	 * Returns crossover chance for this crossover object.
	 * 
	 * @return Currently set crossover chance.
	 */
	public Double getCrossoverChance() {
		return crossoverChance;
	}

	/**
	 * Sets new crossover chance for crossover object.
	 * 
	 * @param crossoverChance New crossover chance to be used for this object.
	 */
	public void setCrossoverChance(Double crossoverChance) {
		this.crossoverChance = crossoverChance;
	}

	/**
	 * Checks if improve specimen policy is enabled.
	 * 
	 * @return true if newly generated specimen are tested against their parents.
	 */
	public boolean isImproveSpecimen() {
		return improveSpecimen;
	}

	/**
	 * Sets new improve specimen policy value.
	 * 
	 * @param improveSpecimen New improve specimen policy value. 
	 */
	public void setImproveSpecimen(boolean improveSpecimen) {
		this.improveSpecimen = improveSpecimen;
	}
	
	/**
	 * Returns String object containing description of crossover configuration in readable form as a summary.
	 * 
	 * @return String description of object configuration.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Crossover class: " + getClass().getSimpleName() + "\n");
		sb.append("Crossover chance: " + crossoverChance + "\n");
		sb.append("Domain control: " + domainControl + "\n");
		sb.append("Improve specimen: " + improveSpecimen + "\n");
		return sb.toString();
	}
	
	/**
	 * Returns configuration panel being extension of JPanel instance.
	 * Returned panel is displayed by graphical user interface allowing user to confgiure crossover.
	 * This method may return null if no configuration for operator is possible.
	 * 
	 * @return Instance of JPanel with initialized components prepared to configure objects properties.
	 */
	@Override
	public JPanel getConfigPanel() {
		return new ClassicCrossoverConfigPanel(this);
	}
}
